from Lib import Util
import State, StatTracker
import sets, string, os
from math import sqrt, log10

class Graph:

    def __init__ (self):
        self.nodes = {}
    	self.numEdges = 0
    	self.numActNodes = 0
    
    # Add a new path to the graph, updating all the parent/child relationships, switch counts, etc
    def startPath (self, path, utcTime):
        if len(path) == 0: #a path with no nodes is no path at all!
            return

        # Create the nodes if they don't already exist
        for name in path:
            if name not in self.nodes:
                self.nodes[name] = Node(name)

        # Update the child and parent relationships
        for i in range(0, len(path)):
            self.nodes[path[i]].rank.updateStat(i, utcTime)
    	    self.numEdges -= self.nodes[path[i]].activeLinkCount()
    	    self.numActNodes -= self.nodes[path[i]].isActiveAsNum()
            if i < len(path) - 1:
                self.nodes[path[i]].addParent(path[i+1], utcTime)
            if i > 0:
                self.nodes[path[i]].addChild(path[i-1], utcTime)
    	    self.numEdges += self.nodes[path[i]].activeLinkCount()
    	    self.numActNodes += self.nodes[path[i]].isActiveAsNum()

    def switchPath (self, oldPath, newPath, utcTime):
        self.startPath(newPath, utcTime)
        self.stopPath(oldPath, utcTime)
	
    def stopPath (self, path, utcTime):
        # Remove every link in the path
        for i in range(0, len(path)):
            self.numEdges -= self.nodes[path[i]].activeLinkCount()
            self.numActNodes -= self.nodes[path[i]].isActiveAsNum()
            if i < len(path) - 1:
                self.nodes[path[i]].stopParent(path[i+1], utcTime)
            if i > 0:
                self.nodes[path[i]].stopChild(path[i-1], utcTime)
            self.numEdges += self.nodes[path[i]].activeLinkCount()
            self.numActNodes += self.nodes[path[i]].isActiveAsNum()	

    def loadNode(self, name, state):
        self.nodes[name] = Node(name, state)
        self.numActNodes += state.isActive() and 1 or 0
        self.numEdges += state.activationCount
        
    def loadNodeChild(self, name, child, childState):
        self.nodes[name].children[child] = childState
        
    def loadNodeParent(self, name, parent, parentState):
        self.nodes[name].parents[parent] = parentState
        
    def ensureValidState(self):
        for node in self.nodes.values():
            if node.isActive():
                assert node.state.activationCount > 0, "%s had activation count %s and was active" % (node.name, node.state.activationCount)
            else:
                assert node.state.activationCount == 0, "%s had activation count %s and was inactive" % (node.name, node.state.activationCount)
            for child,state in node.children.items():
                if state.isActive():
                    assert state.activationCount > 0, "Child %s of %s has activation count %s and was active" % (child, node.name, state.activationCount)
                else:
                    assert state.activationCount == 0, "Child %s of %s has activation count %s and was inactive" % (child, node.name, state.activationCount)
            for parent,state in node.parents.items():
                if state.isActive():
                    assert state.activationCount > 0, "Parent %s of %s has activation count %s and was active" % (parent, node.name, state.activationCount)
                else:
                    assert state.activationCount == 0, "Parent %s of %s has activation count %s and was inactive" % (parent, node.name, state.activationCount)                    
        
    #Reset the timers, start time, activation count for each node - used to 'reset' experiment as if starting at new time with existing paths
    def resetStats(self, utcTime):
        for node in self.nodes.keys():
            self.nodes[node].resetNode(utcTime)

    def nodeCount (self):
        return len(self.nodes)

    def activeNodeCount(self):
        return self.numActNodes

    def peerCount (self):
        return len(self.peers)

    def edgeCount (self):
        return self.numEdges/2

    def nodeStates(self):
        nodeStatesDict = {}
        for node in self.nodes.values():
            nodeStatesDict[node.name] = node.state
        return nodeStatesDict
    
    def nodeRanks(self, utcTime):
        nodeRanksDict = {}
        for node in self.nodes.values():
            nodeRanksDict[node.name] = node.rank
        return nodeRanksDict
    
    def childStates(self):
        childStatesDict = {}
        for node in self.nodes.values():
            for child, state in node.children.items():
                childStatesDict["%s_%s" % (node.name, child)] = state
        return childStatesDict
                
    def parentStates(self):
        parentStatesDict = {}
        for node in self.nodes.values():
            for parent, state in node.parents.items():
                parentStatesDict["%s_%s" % (node.name, parent)] = state
        return parentStatesDict

    def printState(self, utcTime, nodeList = None): #used for debugging
    	#should print node, times switch, time active
    	#should print parents, whether active, time active
        print "Printing state at utc time %s" % (utcTime)
        if not nodeList:
            nodeList = [int(name) for name in self.nodes.keys()]
        nodeList.sort()
    	for name in nodeList:
            node = self.nodes[name]
    	    actTime = node.state.activeTime(utcTime)
            print "%s[%s] - Act Link: %s N-Switches: %s Act Time: %s" %\
            (node.name, node.isActive(), node.activeLinkCount(), node.switchCount(), actTime)
            for name, state in node.parents.items():
                actTime = state.activeTime(utcTime)
                print '\t%s[%s] - Act Time: %s Act Count: %s' % (name, state.isActive(), actTime, state.activationCount)
        print ""
                   
class Node:

    def __init__ (self, name = None, initState = None):
        self.name = name
        if initState:
            self.state = initState
        else:
            self.state = State.State()
        self.parents = {}
        self.children = {}
        self.rank = StatTracker.StatTracker()

    def addParent (self, parent, utcTime):
        if parent not in self.parents:
            self.parents[parent] = State.State()
            
        if self.parents[parent].activate(utcTime):
            self.parents[parent].addSwitch()            
            self.state.activate(utcTime)
            self.state.addSwitch()        

    def addChild (self, child, utcTime):
        if child not in self.children:
            self.children[child] = State.State()
        
        if self.children[child].activate(utcTime):
            self.children[child].addSwitch()
            self.state.activate(utcTime)

    def stopParent(self, parent, utcTime):
        if parent in self.parents:
            if self.parents[parent].deactivate(utcTime):
                self.state.addSwitch()
                self.state.deactivate(utcTime)

    def stopChild(self, child, utcTime):
        if child in self.children:
            if self.children[child].deactivate(utcTime):
                self.state.deactivate(utcTime)

    def resetNode(self, utcTime):
        self.state.reset(utcTime)
        for child in self.children:
            self.children[child].reset(utcTime)
        for parent in self.parents:
            self.parents[parent].reset(utcTime)
	
    def parentCount (self):
        return len(self.parents)

    def activeParentCount (self):
        return sum([1 for state in self.parents.values() if state.isActive()])

    def childCount (self):
        return len(self.children)
    
    def activeChildCount(self):
        return sum([1 for state in self.children.values() if state.isActive()])

    def switchCount(self):
        return self.state.switchCount

    def isActive(self):
        return self.state.isActive()

    def isActiveAsNum(self):
        return self.state.isActive() and 1 or 0

    def activeLinkCount(self):
        return self.state.activationCount
